Skip to content

Conversation

@TheJulianJES
Copy link
Member

@TheJulianJES TheJulianJES commented Oct 31, 2025

Proposed change

This PR fixes an issue where the ZHA migration completely fails if the user unplugs the old or new radio in between steps.
New dialogs are added in the config/options flow to allow the user to plug back in the old or new adapter respectively.

Initial implementation of puddly's suggestion here: zigpy/backlog#59 (comment)

No user-facing changes with old + new adapters plugged in

If both adapters are plugged in during the entire migration (which we expected when it was rewritten), nothing changes!
These dialogs are only shown if the user unplugged the old or new adapter during the migration, when we need to communicate with it to (1) reset or (2) restore the backup.

Images of new dialogs

Old adapter not found for reset New adapter not found for restore
image Bildschirmfoto 2025-10-31 um 04 09 20
Previous dialog for old adapter being unplugged without the warning (click to view)

This is the initial version of the dialog, before the change introduced in this commit: f594696 (#155537)

Bildschirmfoto 2025-10-31 um 04 09 03

Config flow steps

When migrating adapters, something like this happens in the background:

  1. Old adapter: Take fresh backup (optional, uses existing backup otherwise)
  2. New adapter: List in serial port selector
  3. New adapter: Probe to test
  4. Old adapter: Resetting
  5. New adapter: Restoring backup

Currently, if the old adapter is unplugged for step 4, the whole migration blows up.
The same also applies if the new adapter is unplugged for step 5.

Why do users do this?

Sometimes, users have a valid reason to unplug the old adapter at that point: they have a device with only one (free) USB port for their Zigbee adapter. So, we should not catastrophically fail anymore.

With this PR, retry steps are added to step 4 and 5:

  • For step 4, the retry dialog also allows the option to skip the reset, as this needs to be done when the user uses the "Migrate" option, but no longer has their old adapter (e.g. if it's broken/lost).
  • For step 5, the retry dialog only allows you to retry, not skip restoring the backup, as that would obviously defeat the purpose of the whole migration. This dialog needs to be added, as users may unplug the new adapter to reset their old adapter in step 4.

Allowing migration with one USB port

Theoretically, this also allows the user to perform a migration with just one free USB port for the Zigbee adapter.
Mostly, you just need to follow the instructions. There are two new main possibilities for this:

Detailed(!) steps for doing migration with one USB port (CLICK TO OPEN)

'Live' migration

  1. Have old/active adapter plugged in
  2. Start migration flow: backup will be taken of old adapter
  3. Before choosing to re-configure or migrate in first step:
  4. Unplug the old adapter
  5. Plug in the new adapter
  6. Choose "Migration", NOT "Re-configure current radio"
  7. Choose new adapter from serial port selector (-> it's probed)
  8. Choose "Migrate automatically" strategy
  9. User will get a prompt to plug in old adapter for resetting it. (New! Failed before)
    This reset can also be skipped (continue with step 17), otherwise proceed normally:
  10. Unplug new adapter
  11. Plug in old adapter
  12. Click "Retry" to retry reset
  13. After successful reset, the user gets a prompt about plugging in the new adapter again. (New! Failed before)
  14. Unplug old adapter
  15. Plug in new adapter
  16. Click "Submit" to try restoring backup to new adapter again
  17. Success: Migration completed with one USB port

Old backup migration

  1. Unplug old adapter
  2. Plug in new adapter
  3. Start migration flow: no backup can be taken of old adapter, as it's not plugged in, so an existing one is used
  4. Choose "Migration", NOT "Re-configure current radio" in first step
  5. Choose new adapter from serial port selector
  6. Choose "Migrate automatically" strategy
  7. User will get a prompt to plug in the old adapter to reset OR skip reset. (New! Failed before)
  8. User skips resetting old adapter, as they won't plug it in*.
  9. Success: Migration continued normally, as new adapter is plugged in the entire time

*: It is also possible to reset the old adapter at this point. It just needs to be plugged in briefly. The new adapter can be unplugged during that time. Another dialog will ask to plug in the new adapter afterwards.
This is very similar to steps 10 to 16 from the "Live migration".

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

@home-assistant
Copy link

Hey there @dmulcahey, @Adminiuga, @puddly, mind taking a look at this pull request as it has been labeled with an integration (zha) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of zha can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign zha Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

Comment on lines 492 to 494
# config entry should always exist here, skip if not
if not config_entries:
return await self.async_step_maybe_confirm_ezsp_restore()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one line missing test coverage. Can write a test for this, but we should never be able to hit this.

We could assert that config_entries is not empty, or we could also save the "old adapter device path" to some instance variable in async_step_maybe_reset_old_radio.
Passing it as an additional argument also works, but it's not a pattern I've seen used for any config flows really.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You conceivably could hit this by deleting ZHA halfway through the flow but I agree, this isn't something we should care about. There are many places in Core that break if you try hard enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the test added I added in bdf3a2c (#155537) won't actually help to cover this, since the plug_in_old_radio won't happen again. The retry_old_radio step goes to maybe_reset_old_radio and that step itself will skip to maybe_confirm_ezsp_restore, as there are no other config entries anymore.

For this line to be hit, the config entry needs to be removed at precisely the exact moment we're trying to reset the adapter. I'll try something different..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bcb573c (#155537) should cover this now and work completely reliable, even if this is almost impossible for a user to do: remove both the config entry and unplug the adapter whilst we try to reset it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative to this would be saving or passing the old adapter's path from async_step_maybe_reset_old_radio. Then, we wouldn't need to get config entries in the new step displaying the old adapter's path, so we could avoid the potential step skip if the entry is removed in time, thus avoiding the whole tests for it as well.

But now, we have the test and everything functions as expected if the user is able to hit this 😄

@TheJulianJES TheJulianJES added this to the 2025.11.0 milestone Nov 4, 2025
@TheJulianJES TheJulianJES marked this pull request as ready for review November 4, 2025 17:17
Copilot AI review requested due to automatic review settings November 4, 2025 17:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds retry logic for handling unplugged Zigbee adapters during the ZHA migration/setup flow. When an adapter connection fails, users are now prompted to plug the adapter back in and retry, rather than the flow failing outright.

Key changes:

  • Added error handling for HomeAssistantError exceptions during adapter reset and restore operations
  • Implemented two new config flow steps: plug_in_old_radio and plug_in_new_radio to handle adapter connection failures
  • Added corresponding translation strings for the new user-facing prompts

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
homeassistant/components/zha/config_flow.py Added error handling and retry steps for when adapters are unplugged during migration/setup
homeassistant/components/zha/strings.json Added translation strings for new plug-in prompts in both config and options flows
tests/components/zha/test_config_flow.py Added comprehensive tests for retry scenarios when adapters are unplugged

@edenhaus
Copy link
Member

edenhaus commented Nov 4, 2025

Will wait on the merge as you comment on copilot review that you will fix it

@TheJulianJES
Copy link
Member Author

TheJulianJES commented Nov 4, 2025

Per "discussion" in the last Copilot comment above (#155537 (comment)), I've also updated the string for the old adapter reset dialog to include the "warning" text. That gives it a bit more emphasis on when you want to do this (or when not).

Also posted screenshots for a comparison of both dialog variants there. If we do not want this, I can just revert the last commit.

@puddly
Copy link
Contributor

puddly commented Nov 4, 2025

Looks good to me!

@TheJulianJES TheJulianJES merged commit 4419c23 into home-assistant:dev Nov 4, 2025
36 checks passed
@TheJulianJES TheJulianJES deleted the tjj/zha_config_flow_guard_old_adapter_removal branch November 4, 2025 19:34
@github-actions github-actions bot locked and limited conversation to collaborators Nov 5, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Core] ZHA migration flow fails if old adapter is unplugged

6 participants